package com.bkhech.boot.autoconfigure.security;

import cn.hutool.core.collection.CollUtil;
import com.bkhech.boot.configure.security.config.AuthorizeRequestsCustomizer;
import com.bkhech.boot.configure.security.core.filter.TokenAuthenticationFilter;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.annotation.Resource;
import javax.annotation.security.PermitAll;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 自定义的 Spring Security 配置适配器实现
 * <p>
 * {@link @EnableGlobalMethodSecurity} : 开启 Spring Security 方法级安全注解
 * <p>
 * prePostEnabled :决定Spring Security的前注解是否可用 [@PreAuthorize,@PostAuthorize,..]
 * secureEnabled : 决定是否Spring Security的保障注解 [@Secured] 是否可用
 * jsr250Enabled ：决定 JSR-250 annotations 注解[@RolesAllowed..] 是否可用.
 *
 * @author guowm
 * @date 2023/1/10
 */
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
@AutoConfiguration
public class WebSecurityConfigurerAdapter {

    @Resource
    private ApplicationContext applicationContext;
    /**
     * Token 认证过滤器 Bean
     */
    @Resource
    private TokenAuthenticationFilter authenticationTokenFilter;

    /**
     * 自定义的权限映射 Bean 集合
     *
     * ObjectProvider：防止没有AuthorizeRequestsCustomizer bean 定义时，出现空指针
     *
     * @see #configure(HttpSecurity)
     */
    @Resource
    private ObjectProvider<List<AuthorizeRequestsCustomizer>> authorizeRequestsCustomizers;

    /**
     * 权限不够处理器 Bean
     */
    @Resource
    private AccessDeniedHandler accessDeniedHandler;
    /**
     * 认证失败处理类 Bean
     */
    @Resource
    private AuthenticationEntryPoint authenticationEntryPoint;

    /**
     * @return
     * @see UserServiceImpl
     */
//    @Bean
//    public UserDetailsService userDetailsService() {
//        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
//        manager.createUser(User.withUsername("zhangsan").password("123456").authorities("p1").build());
//        manager.createUser(User.withUsername("lisi").password("123456").authorities("p2").build());
//        return manager;
//    }
//    @Bean
//    public PasswordEncoder passwordEncoder() {
//        return NoOpPasswordEncoder.getInstance();
//    }

    /**
     * 静态资源设置
     */
    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return web -> web.ignoring()
                .antMatchers(
                        "/",
                        "/favicon.ico"
                );
    }

    /**
     * 配置 URL 的安全配置
     * <p>
     * anyRequest          |   匹配所有请求路径
     * access              |   SpringEl表达式结果为true时可以访问
     * anonymous           |   匿名可以访问, 未登录的用户
     * denyAll             |   用户不能访问
     * fullyAuthenticated  |   用户完全认证可以访问（非remember-me下自动登录）
     * hasAnyAuthority     |   如果有参数，参数表示权限，则其中任何一个权限可以访问
     * hasAnyRole          |   如果有参数，参数表示角色，则其中任何一个角色可以访问
     * hasAuthority        |   如果有参数，参数表示权限，则其权限可以访问
     * hasIpAddress        |   如果有参数，参数表示IP地址，如果用户IP和参数匹配，则可以访问
     * hasRole             |   如果有参数，参数表示角色，则其角色可以访问
     * permitAll           |   用户可以任意访问
     * rememberMe          |   允许通过remember-me登录的用户访问
     * authenticated       |   用户登录后可访问
     */
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                // 开启跨域
                .cors().and()
                // formLogin: 开启表单登录配置
                // loginPage: 配置登录页面地址, 默认是自动生成的界面
                // successHandler(): 登录成功之后的处理器
//                 .formLogin().loginProcessingUrl("/login").successHandler(authenticationSuccessHandler()).failureHandler(authenticationFailureHandler()).and()

                // 基于 token 机制，所以不需要 Session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                //解决 in a frame because it set 'X-Frame-Options' to 'DENY' 问题
                .headers().frameOptions().disable().and()
                // CSRF 禁用，因为不使用 Session
                .csrf().disable()
                // 一堆自定义的 Spring Security 处理器
                .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).accessDeniedHandler(accessDeniedHandler);

        // 登录暂时不使用 Spring Security 的拓展点，主要考虑一方面拓展多用户、多种登录方式相对复杂，一方面用户的学习成本较高

        // 获得 @PermitAll 带来的 URL 列表，免登录
        final MultiValueMap<HttpMethod, String> permitAllUrls = getPermitAllUrlsFromAnnotations();
        // 设置每个请求的权限
        httpSecurity
                // 1 全局共享规则
                // 表示开启权限配置
                .authorizeRequests()
                // 1.0 测试：permitAll() 表示请求(用户)可以任意访问，/hello测试地址
                .antMatchers("/hello").permitAll()
                // 1.1 静态资源可匿名访问
                .antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").anonymous()
                // 1.2 设置 @PermitAll 无需认证
                .antMatchers(HttpMethod.GET, permitAllUrls.getOrDefault(HttpMethod.GET, List.of()).toArray(new String[0])).permitAll()
                .antMatchers(HttpMethod.POST, permitAllUrls.getOrDefault(HttpMethod.POST, List.of()).toArray(new String[0])).permitAll()
                .antMatchers(HttpMethod.PUT, permitAllUrls.getOrDefault(HttpMethod.PUT, List.of()).toArray(new String[0])).permitAll()
                .antMatchers(HttpMethod.DELETE, permitAllUrls.getOrDefault(HttpMethod.DELETE, List.of()).toArray(new String[0])).permitAll()
                // 2 每个项目的自定义规则
                .and().authorizeRequests(this::authorizeRequestsCustomizer)
                // 3 兜底认证规则
                // 表示其他任何尚未匹配的URL请求(用户)，要经过认证(用户登录)之后才能访问
                .authorizeRequests()
                .anyRequest().authenticated();

        // 添加 Token Filter
        httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

        return httpSecurity.build();
    }

    private MultiValueMap<HttpMethod, String> getPermitAllUrlsFromAnnotations() {
        MultiValueMap<HttpMethod, String> result = new LinkedMultiValueMap<>();
        // 获得接口对象的 HandlerMethod 集合
        final RequestMappingHandlerMapping requestMappingHandlerMapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
        final Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();

        // 获得有 @PermitAll 注解的接口
        for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethodMap.entrySet()) {
            final HandlerMethod handlerMethod = entry.getValue();
            if (!handlerMethod.hasMethodAnnotation(PermitAll.class)) {
                continue;
            }
            final RequestMappingInfo requestMappingInfo = entry.getKey();
            if (requestMappingInfo.getPathPatternsCondition() == null) {
                continue;
            }
            final Set<String> urls = requestMappingInfo.getPathPatternsCondition().getPatternValues();
            // 根据请求方法，添加到 result 结果
            requestMappingInfo.getMethodsCondition().getMethods().forEach(requestMethod -> {
                switch (requestMethod) {
                    case GET:
                        result.addAll(HttpMethod.GET, CollUtil.newArrayList(urls));
                        break;
                    case POST:
                        result.addAll(HttpMethod.POST, CollUtil.newArrayList(urls));
                        break;
                    case PUT:
                        result.addAll(HttpMethod.PUT, CollUtil.newArrayList(urls));
                        break;
                    case DELETE:
                        result.addAll(HttpMethod.DELETE, CollUtil.newArrayList(urls));
                        break;
                    default:
                }
            });
        }
        return result;
    }

    private void authorizeRequestsCustomizer(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {
        // 循环设置自定义规则
        authorizeRequestsCustomizers.ifAvailable(customizers -> customizers.forEach(customizer -> customizer.customize(registry)));
    }
}
